﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Xml.Linq;

namespace XEnamel
{
	public class DynamicMethod : DynamicObject, IEnumerable<Symbol>
	{
		#region Variables

		private List<Symbol> _list;
		private Expression _expression;
		private Delegate _delegate;

		#endregion

		#region Constructors

		private DynamicMethod()
		{
			_list = new List<Symbol>();
		}

		private DynamicMethod(Expression expr)
			: this()
		{
			Expression = expr;
		}

		/// <summary>
		/// Create a dynamic method from an xml element.
		/// </summary>
		/// <param name="node"></param>
		private DynamicMethod(XElement node)
			: this(new XmlToExpression().Translate(node))
		{
		}

		/// <summary>
		/// Load a dynamic method from an xml file.
		/// </summary>
		private DynamicMethod(string filename)
			: this(XElement.Load(filename))
		{
		}

		/// <summary>
		/// Load a dynamic method from an xml stream.
		/// </summary>
		private DynamicMethod(Stream stream)
			: this(XElement.Load(stream))
		{
		}

		#endregion

		#region Properties

		/// <remarks>
		/// If it isn't defined yet, it will be defined and set to null.
		/// </remarks>
		public dynamic this[string name]
		{
			get
			{
				return GetSymbol(name).Data;
			}
			set
			{
				EnsureThatSymbolExists(name);
				_list.Where(x => x.Name == name).First().Data = value;
			}
		}

		public Expression Expression
		{
			get
			{
				return _expression;
			}
			set
			{
				_expression = value;
				if (_expression.NodeType != ExpressionType.Lambda)
				{
					_expression = Expression.Lambda(_expression);
				}

				var lambda = _expression as LambdaExpression;
				//if ((lambda.Parameters.Count == 0) || (lambda.Parameters.First().Type != typeof(DynamicMethod)))
				//{
				//    var parameters = lambda.Parameters.Select(x => Expression.Parameter(x.Type, x.Name)).ToArray();
				//    lambda = Expression.Lambda(Expression.Invoke(lambda, new[] { Expression.Parameter(typeof(DynamicMethod), "self") }.Concat(parameters)), parameters);
				//}

				_delegate = lambda.Compile();
			}
		}

		#endregion

		#region Methods

		#region I/O Methods

		public XElement ToXml()
		{
			return ExpressionToXml.Translate(Expression);
		}

		public override string ToString()
		{
			return _expression.ToString();
		}

		/// <summary>
		/// Save the method to an xml file.
		/// </summary>
		public void Save(string filename)
		{
			ToXml().Save(filename);
		}

		/// <summary>
		/// Save the method to an xml file.
		/// </summary>
		public void Save(Stream stream)
		{
			ToXml().Save(stream);
		}

		public static DynamicMethod Load(Expression expr)
		{
			return new DynamicMethod(expr);
		}

		public static DynamicMethod Load(XElement node)
		{
			return new DynamicMethod(node);
		}

		public static DynamicMethod Load(string filename)
		{
			return new DynamicMethod(filename);
		}

		public static DynamicMethod Load(Stream stream)
		{
			return new DynamicMethod(stream);
		}

		#endregion

		#region Dynamic Access Methods

		public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
		{
			if (_delegate == null)
			{
				result = null;
				return false;
			}

			try
			{
				result = _delegate.DynamicInvoke(new object[] { this }.Concat(args).ToArray());
			}
			catch (TargetInvocationException ex)
			{
				throw ex.InnerException; // throw the real exception
			}
			catch
			{
				result = _delegate.DynamicInvoke(args);
			}
			return true;
		}

		public override bool TryGetMember(GetMemberBinder binder, out object result)
		{
			result = this[binder.Name];
			return true;
		}

		public override bool TrySetMember(SetMemberBinder binder, object value)
		{
			this[binder.Name] = value;
			return true;
		}

		public override IEnumerable<string> GetDynamicMemberNames()
		{
			return _list.Select(x => x.Name);
		}

		#endregion

		/// <summary>
		/// This is used by ExpressionEx.SymbolExpression to retrieve a symbol.
		/// </summary>
		public Symbol GetSymbol(string name)
		{
			EnsureThatSymbolExists(name);
			return _list.Where(x => x.Name == name).First();
		}

		public IEnumerator<Symbol> GetEnumerator()
		{
			return _list.GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return _list.GetEnumerator();
		}

		private void EnsureThatSymbolExists(string symbolName)
		{
			if (!_list.Any(x => x.Name == symbolName))
			{
				_list.Add(new Symbol() { Name = symbolName, Data = null });
			}
		}

		#endregion
	}
}